home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 5
/
Aminet 5 - March 1995.iso
/
Aminet
/
util
/
moni
/
PriMan20.lha
/
PriMan
/
Source
/
Window.c
< prev
Wrap
C/C++ Source or Header
|
1994-11-13
|
46KB
|
1,361 lines
/*
* Task Priority Manager
* Copyright 1993, 1994 Barry McConnell
* bmccnnll@tcd.ie
*
* Code to open the main window, settings window, and build the task list.
*
* Set tab stops to 4 when editing this file.
*/
#include "PriMan.h"
/*
* Layout the gadgets for the main window, and open it.
*/
void OpenMainWindow(void)
{
WORD buttonText, /* width of Settings button */
sizeHeight, /* height of window size gadget */
spacing, /* spacing between buttons */
/*
* The next few variables hold various window
* sizes, explained later.
*/
listWidth, listHeight, gadHeight, width1, width2;
BOOL fontError; /* already warned user about font error */
struct TextExtent fontSize; /* structure containing font size */
struct RastPort myRast; /* used to check widths of text strings */
struct DrawInfo *drawInfo; /* BOOPSI needs this */
struct Image *sizeImage; /* window size gadget image */
struct NewGadget myGad; /* structure used when creating gadgets */
struct Gadget *prevGad; /* last gadget created */
struct NewMenu menu[] = /* the main window's menu structure */
{
{ NM_TITLE, "Project", 0, 0, 0, 0 },
{ NM_ITEM, "Settings...", "S", 0, 0, 0 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "About...", "?", 0, 0, 0 },
{ NM_ITEM, "Help...", 0, 0, 0, 0 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "Hide", "H", 0, 0, 0 },
{ NM_ITEM, "Quit", "Q", 0, 0, 0 },
{ NM_TITLE, "Task", 0, 0, 0, 0 },
{ NM_ITEM, "Update List", "U", 0, 0, 0 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "Kill...", "K", 0, 0, 0 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "Signal...", 0, 0, 0, 0 },
{ NM_SUB, "Ctrl-C", 0, 0, 0, (APTR)SIGBREAKF_CTRL_C },
{ NM_SUB, "Ctrl-D", 0, 0, 0, (APTR)SIGBREAKF_CTRL_D },
{ NM_SUB, "Ctrl-E", 0, 0, 0, (APTR)SIGBREAKF_CTRL_E },
{ NM_SUB, "Ctrl-F", 0, 0, 0, (APTR)SIGBREAKF_CTRL_F },
{ NM_ITEM, "Priority", 0, 0, 0, 0 },
{ NM_SUB, "-5", "-", 0, 0, (APTR)-5 },
{ NM_SUB, "-1", 0, 0, 0, (APTR)-1 },
{ NM_SUB, "0", "=", 0, 0, (APTR)0 },
{ NM_SUB, "+1", 0, 0, 0, (APTR)+1 },
{ NM_SUB, "+5", "+", 0, 0, (APTR)+5 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "Frozen", "F", CHECKIT | MENUTOGGLE, 0, 0 },
{ NM_ITEM, NM_BARLABEL, 0, 0, 0, 0 },
{ NM_ITEM, "Wide Slider", "W", CHECKIT | MENUTOGGLE, 0, 0 },
{ NM_END, NULL, 0, 0, 0, 0 }
};
/*
* Message that appears when one of the fonts couldn't be opened.
*/
struct EasyStruct font =
{
sizeof(struct EasyStruct),
0,
"PriMan trouble",
"Can't open %s -\ndefaulting to Topaz 8.",
"Okay"
};
/*
* Perform all the necessary stuff before opening any windows, such as
* locking the public screen and getting the fonts. If the window is
* already open, this step is not necessary.
*/
if (!mainWindow)
{
if (myScreen = LockOurScreen(TRUE))
{
if ((visInfo) = GetVisualInfo(myScreen, TAG_END))
{
/*
* If the first character of the font name is 0, or the
* height is 0, it means the user never gave a font name on
* startup. In this case, we use the Preferences fonts.
*/
if (!propName[0] || !propTA.ta_YSize)
{
strcpy(propName, myScreen -> Font -> ta_Name);
propTA.ta_YSize = myScreen -> Font -> ta_YSize;
}
if (!monoName[0] || !monoTA.ta_YSize)
{
strcpy(monoName, GfxBase -> DefaultFont -> tf_Message.mn_Node.ln_Name);
monoTA.ta_YSize = GfxBase -> DefaultFont -> tf_YSize;
}
/*
* Now we try to open the fonts. If either font fails to
* open, we attempt to fall back on Topaz 8. fontError is
* set if there is a problem with the Gadget font, so we
* know not to bother the user with a second requester if
* we can't open the List font either.
*/
fontError = FALSE;
if (!(propFont = OpenDiskFont(&propTA)))
{
SimpleRequest(&font, propName);
fontError = TRUE;
propTA.ta_YSize = 8;
strcpy(propName, "topaz.font");
propFont = OpenDiskFont(&propTA);
}
if (!(monoFont = OpenDiskFont(&monoTA)))
{
if (!fontError)
SimpleRequest(&font, monoName);
monoTA.ta_YSize = 8;
strcpy(monoName, "topaz.font");
monoFont = OpenDiskFont(&monoTA);
}
/*
* If both fonts opened okay, we figure out their sizes in
* pixels. Else, we abort - but Topaz 8 *should* be around!
*/
if (propFont && monoFont)
{
FontExtent(propFont, &fontSize);
height = fontSize.te_Height;
FontExtent(monoFont, &fontSize);
sysHeight = fontSize.te_Height;
sysWidth = fontSize.te_Width;
/*
* If this is the very first time the main window has
* been opened, and the user did not supply any default
* dimensions, we estimate a good size. The height will
* will be 2/3 the screen height, and the width will
* be half the height. The top and left positions will
* be chosen so as to centre the window on the screen.
*/
if (!winHeight)
winHeight = myScreen -> Height * 2 / 3;
if (!winWidth)
winWidth = winHeight / 2;
if (winTop == -1)
winTop = (myScreen -> Height - winHeight) / 2;
if (winLeft == -1)
winLeft = (myScreen -> Width - winWidth) / 2;
}
else
error = FONT_ERROR;
}
else
error = VISINFO_ERROR;
}
else
error = LOCK_ERROR;
}
if (!error)
{
/*
* Calculate the width of the widest button. This is done using a
* dummy RastPort for getting string lengths.
*/
InitRastPort(&myRast);
myRast.Font = propFont;
buttonText = TextLength(&myRast, "Settings...", 11) + INTERWIDTH * 2;
/*
* Calculate the height of the bottom window border. This is
* dependent on the dimensions of the size gadget, which can
* change using sysihack.
*/
drawInfo = GetScreenDrawInfo(myScreen);
sizeImage = (struct Image *)NewObject(NULL, "sysiclass",
SYSIA_DrawInfo, drawInfo,
SYSIA_Which, SIZEIMAGE,
SYSIA_Size, SYSISIZE_MEDRES,
TAG_END);
sizeHeight = sizeImage -> Height;
DisposeObject((APTR)sizeImage);
FreeScreenDrawInfo(myScreen, drawInfo);
/*
* Here we calculate the minimum window size we need to fit all
* the gadgets. We need to see if the minimum space the ListView
* needs is larger or smaller than the minimum space occupied by
* the buttons.
*
* windowTop is the size of the window's top border plus the title
* bar font.
*
* listWidth is the width of the window using a ListView with no
* characters in it. It takes into account the gap needed between
* the window borders on either side, and the width of the
* ListView's own borders. A bug in 2.x clips the ListView text
* too early (before the right border) when using certain fonts, so
* we must allow for that.
*
* listHeight allows for one or two elements in the ListView. It
* takes into account the space occupied by the ListView's borders,
* and the fact that under 2.x extra space is taken up by the name
* of the currently-selected entry being displayed below.
*
* gadHeight is the vertical space occupied by everything *else* in
* the window, namely the slider gadget, the row of buttons, the
* spacing between everything, and the size gadget in the bottom
* border.
*
* width1 is the width of the window using the smallest possible
* ListView. It uses the ListView's "baggage" plus the space taken
* by enough characters in it to contain the first letter of each
* task name along with the priority. Worst case here would be a
* frozen task (e.g. Workbench) running at a high priority: we need
* to have enough room for "(W) -128", i.e. 8 characters.
*
* width2 is simply the width of the three buttons with spacing
* between them and on either side.
*/
windowTop = (myScreen -> WBorTop) + myScreen -> Font -> ta_YSize + 1;
listWidth = INTERWIDTH + (osver < 39 ? ListEarlyClip : 0) + INTERWIDTH + ScrollBarWidth + INTERWIDTH;
listHeight = INTERHEIGHT + sysHeight * (osver < 39 ? 4 : 2);
gadHeight = windowTop + INTERHEIGHT + /* ListView */ INTERHEIGHT + (sysHeight + INTERHEIGHT) + INTERHEIGHT +
(height + INTERHEIGHT) + INTERHEIGHT + sizeHeight;
width1 = sysWidth * 8 + listWidth;
width2 = INTERWIDTH + (buttonText + INTERWIDTH) * 3;
minWidth = width1 > width2 ? width1 : width2;
minHeight = gadHeight + listHeight;
/*
* Even using a 24-point font, a minimally-sized PriMan window can
* still fit on a 640*200 screen. Therefore, there is no need to
* ever drop down to Topaz 8 to "force" it to fit - making the
* window smaller will always suffice. (Using a 50-point font on a
* low-res screen would be plain silly!) Here we also make sure the
* window is going to be at least as large as the minimum size
* needed. If the window has already been opened, these lines are
* not necessary since it's guaranteed to fit already, so instead
* we take the opportunity to copy the physical window dimensions
* into the variables.
*/
if (mainWindow)
GetPos();
else
{
/*
* Make sure window dimensions fall within the accepted range.
*/
Range(&winWidth, minWidth, myScreen -> Width);
Range(&winHeight, minHeight, myScreen -> Height);
}
/*
* Now that we know the true width of the window we can, firstly,
* calculate how many characters will fit in the ListView...
*/
taskLength = (winWidth - listWidth) / sysWidth;
/*
* ...and, secondly, calculate the spacing between each button.
* This is done by taking the window width, subtracting the amount
* taken up by the buttons themselves and the border space, then
* dividing by one less than the number of buttons on the row.
*/
spacing = (winWidth - buttonText * 3 - INTERWIDTH * 2) / 2;
/*
* Now we have to create all the gadgets. We use prevGad to link
* them together. If one call fails, then this will become NULL,
* and the succeeding calls will automatically fail. Thus we need
* only check prevGad after attempting to create all the gadgets.
*/
mainGads = NULL;
prevGad = CreateContext(&mainGads);
/*
* Create the ListView.
*/
myGad.ng_LeftEdge = INTERWIDTH;
myGad.ng_TopEdge = windowTop + INTERHEIGHT; /* leave a gap below title bar */
myGad.ng_Width = winWidth - INTERWIDTH * 2; /* leave a gap on either side */
myGad.ng_Height = winHeight - gadHeight; /* maximum space left over for ListView */
myGad.ng_GadgetText = NULL;
myGad.ng_TextAttr = &monoTA;
myGad.ng_GadgetID = LISTGAD;
myGad.ng_Flags = 0;
myGad.ng_VisualInfo = visInfo;
prevGad = listGad = CreateGadget(LISTVIEW_KIND, prevGad, &myGad,
GTLV_Labels, NULL,
GTLV_ShowSelected, NULL,
TAG_END);
/*
* The ListView may get shrunk slightly, to accomodate an exact
* number of lines. If the window has not already been opened, we
* can check the created ListView's height, and shrink the window
* appropriately. Under 2.x, the correct height does not get filled
* into the Gadget structure, so this only works under 3.x.
*/
if (!(mainWindow || osver < 39))
{
winHeight += (listGad) -> Height - myGad.ng_Height;
myGad.ng_Height = (listGad) -> Height;
}
/*
* Create the slider. Even though it gets enabled or disabled as
* necessary later on, we assume the currently-selected task (if
* any) will remain valid, and enable or disable it as necessary
* here. This prevents it "flashing" when the window gets resized.
*/
myGad.ng_LeftEdge += sysWidth * 4 + INTERWIDTH; /* allow room for the label */
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_Width = winWidth - INTERWIDTH - myGad.ng_LeftEdge;
myGad.ng_Height = sysHeight + INTERHEIGHT;
myGad.ng_GadgetID = SLIDERGAD;
prevGad = sliderGad = CreateGadget(SLIDER_KIND, prevGad, &myGad,
GTSL_Min, -25,
GTSL_Max, 25,
GTSL_Level, 0,
GTSL_LevelFormat, "%4ld",
GTSL_MaxLevelLen, 4,
GTSL_LevelPlace, PLACETEXT_LEFT,
GA_RelVerify, TRUE,
GA_Disabled, pos == -1,
TAG_END);
/*
* Create the Break button.
*/
myGad.ng_LeftEdge = INTERWIDTH;
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_Width = buttonText;
myGad.ng_Height = height + INTERHEIGHT;
myGad.ng_GadgetText = "_Break";
myGad.ng_TextAttr = &propTA;
myGad.ng_GadgetID = BREAKGAD;
prevGad = breakGad = CreateGadget(BUTTON_KIND, prevGad, &myGad,
GT_Underscore, '_',
GA_Disabled, pos == -1,
TAG_END);
/*
* Create the Kill button, using the spacing variable to decide how
* far right it goes relative to the Break button.
*/
myGad.ng_LeftEdge += buttonText + spacing;
myGad.ng_GadgetText = "_Kill";
myGad.ng_GadgetID = KILLGAD;
prevGad = killGad = CreateGadget(BUTTON_KIND, prevGad, &myGad,
GT_Underscore, '_',
GA_Disabled, pos == -1,
TAG_END);
/*
* Create the Settings button. This always goes relative to the
* right window border.
*/
myGad.ng_LeftEdge = winWidth - INTERWIDTH - buttonText;
myGad.ng_GadgetText = "_Settings...";
myGad.ng_GadgetID = SETTINGSGAD;
prevGad = setGad = CreateGadget(BUTTON_KIND, prevGad, &myGad,
GT_Underscore, '_',
TAG_END);
/*
* Once we've got this far, we need to check if all the gadgets
* actually got created. Next, if the window was was already open
* (i.e. this function was called because of a resize), any
* remaining imagery inside its borders will need clearing, and we
* can simply add the gadgets straight in. The alternative requires
* opening the window and attaching the menu strip.
*/
if (prevGad)
{
if (mainWindow)
{
/*
* When clearing the window's old imagery, everything
* should be wiped except the following:
*
* - Title bar and space underneath it
* - Left border and space inside it
* - Right border
* - Bottom border
*/
blank.Width = winWidth - INTERWIDTH - INTERWIDTH / 2;
blank.Height = winHeight - windowTop - INTERHEIGHT - sizeHeight;
EraseImage(mainWindow -> RPort, &blank, INTERWIDTH, windowTop + INTERHEIGHT);
AddGList(mainWindow, mainGads, ~0, -1, NULL);
RefreshGList(mainGads, mainWindow, NULL, -1);
GT_RefreshWindow(mainWindow, NULL);
CreateList(&memoryKey);
}
else
{
if (mainWindow = OpenWindowTags(NULL,
WA_Title, "PriMan",
WA_ScreenTitle, "Task Priority Manager "VERSION" - Copyright 1994 Barry McConnell",
WA_Gadgets, mainGads,
WA_Left, winLeft,
WA_Top, winTop,
WA_Width, winWidth,
WA_Height, winHeight,
WA_MinWidth, minWidth,
WA_MinHeight, minHeight,
WA_MaxWidth, ~0,
WA_MaxHeight, ~0,
WA_DragBar, TRUE,
WA_DepthGadget, TRUE,
WA_CloseGadget, TRUE,
WA_SizeGadget, TRUE,
WA_SizeBBottom, TRUE,
WA_Activate, TRUE,
WA_NewLookMenus, TRUE,
refresh == SIMPLEWINDOW ? WA_SimpleRefresh : WA_SmartRefresh, TRUE,
WA_PubScreen, myScreen,
TAG_END))
{
if (menuStrip = CreateMenus(menu, TAG_END))
{
/*
* We want to get the initial state of the "..."
* after the Kill and Signal menu items right, so
* we use this function, which also attaches the
* menu strip to our window. If there is an error
* here, the tidyup routine will free up the menu
* strip for us, since the main window is already
* open.
*/
MenuEllipsis(menuStrip, FALSE);
if (!error)
{
/*
* If we're not running as a Commodity and
* can't be iconified, there is no point in
* allowing the user to select the Hide menu
* item!
*/
if (!(iconify || commodity))
OffMenu(mainWindow, FULLMENUNUM(M_PROJECT, I_HIDE, NOSUB));
/*
* Set up the message port, refresh the window,
* add in the task list, and make sure our
* screen is visible.
*/
mainWindow -> UserPort = winPort;
ModifyIDCMP(mainWindow, WINDOWIDCMP);
GT_RefreshWindow(mainWindow, NULL);
CreateList(&memoryKey);
ScreenToFront(myScreen);
}
}
else
error = MENU_ERROR;
}
else
error = MAIN_ERROR;
}
}
else
error = GADGET_ERROR;
}
}
/*
* Layout the gadgets for the settings window, and open it. There are
* actually going to be three sets of gadgets for this window, with the
* user selecting one set at a time. So we create all three sets here, and
* then use the one which the user had selected the last time this window
* was open.
*/
void OpenSettingsWindow(void)
{
WORD oldPage, /* last gadget page showing when settings window was opened */
/*
* These relate to the size of the checkbox gadget,
* and are explained later.
*/
size, offset, max,
/*
* These relate to the window dimensions, and are
* also explained later.
*/
buttonWidth, textWidth, cancelWidth, spacing, intWidth, innerWidth, setWidth,
/*
* End of each page of gadgets. gadgetEnd itself is
* the lowest position the gadgets reach to.
*/
gadgetEnd1, gadgetEnd2, gadgetEnd3, gadgetEnd;
struct RastPort myRast; /* used to check widths of text strings */
struct NewGadget myGad; /* structure used when creating gadgets */
struct Gadget *prevGad1, /* last gadget created - one for each page of gadgets */
*prevGad2, *prevGad3, *prevGad4;
/*
* These contain the text for the three cycle gadgets.
*/
static char *pageText[] = { "Interface", "Commodity", "General", NULL };
static char *refreshText[] = { "Smart Refresh", "Simple Refresh", NULL };
static char *openText[] = { "Default Screen", "Front Screen", NULL };
/*
* Here we remember the original settings of everything, so they can be
* restored if the user clicks Cancel.
*/
newPropFont = newMonoFont = FALSE;
tempRefresh = refresh;
tempOpen = open;
tempConfirm = confirm;
tempIconify = iconify;
tempCommodity = commodity;
tempPopup = popup;
FontString(&propTA, propString); /* initial setting for the text box */
FontString(&monoTA, monoString);
strcpy(tempPropName, propName); /* initial settings for the font requesters */
strcpy(tempMonoName, monoName);
tempPropSize = propTA.ta_YSize;
tempMonoSize = monoTA.ta_YSize;
InitRastPort(&myRast);
myRast.Font = propFont;
/*
* Under 3.x, we can scale the checkboxes to match the font size. The
* following variables are used:
*
* size holds the actual height of the checkbox. It takes into account
* whether or not it can be scaled, and has a minimum size.
*
* offset is non-zero if the font is taller than the checkbox, and
* gives the extra amount the checkbox will need to be moved down to be
* centred with respect to its label.
*
* max is the actual height taken by the checkbox and label
* side-by-side, and is just the maximum of their respective heights,
* used for lining up gadgetry below them.
*/
size = osver < 39 ? 11 : (height > 10 ? height : 11);
offset = height > size ? (height - size) / 2 : 0;
max = height > size ? height : size;
/*
* Now we calculate the widths of a few things, to help us lay out the
* interface:
*
* buttonWidth is the width of each Font button.
*
* textWidth is the width of the text boxes beside the buttons. For
* want of anything better to base this on, we'll use the width of the
* two cycle gadget below it for this.
*
* cancelWidth is the width of the Save, Use and Cancel buttons.
*
* spacing is the distance between these three buttons.
*
* intWidth is the size of any integer boxes (enough to hold "-128"
* plus the cursor).
*
* innerWidth is the width of the portion of the window inside the
* bevel box, allowing a gap.
*
* setWidth is the width of the whole window, basically just allowing
* a gap for the bevel box.
*/
buttonWidth = TextLength(&myRast, "Gadget Font...", 14) + INTERWIDTH * 2;
textWidth = TextLength(&myRast, "Simple Refresh", 14) + INTERWIDTH * 2 + CycleWidth;
cancelWidth = TextLength(&myRast, "Cancel", 6) + INTERWIDTH * 2;
intWidth = TextLength(&myRast, "-9999", 5) + INTERWIDTH * 2;
innerWidth = INTERWIDTH + buttonWidth + INTERWIDTH + textWidth + INTERWIDTH;
setWidth = INTERWIDTH + innerWidth + INTERWIDTH;
spacing = (setWidth - INTERWIDTH * 2 - cancelWidth * 3) / 2;
/*
* Create and link all the gadgets, as before. Each set of gadgets is
* pointed to from an array.
*/
setGads[3] = NULL;
prevGad1 = CreateContext(&(setGads[3]));
/*
* Create the cycle gadget at the top of the window. This is stretched
* to be as wide as possible.
*/
myGad.ng_LeftEdge = INTERWIDTH + TextLength(&myRast, "Page", 4) + INTERWIDTH;
myGad.ng_TopEdge = windowTop + INTERHEIGHT;
myGad.ng_Width = setWidth - myGad.ng_LeftEdge - INTERWIDTH;
myGad.ng_Height = height + INTERHEIGHT + 2;
myGad.ng_GadgetText = "_Page";
myGad.ng_GadgetID = PAGEGAD;
myGad.ng_TextAttr = &propTA;
myGad.ng_Flags = PLACETEXT_LEFT;
myGad.ng_VisualInfo = visInfo;
prevGad1 = pageGad = CreateGadget(CYCLE_KIND, prevGad1, &myGad,
GT_Underscore, '_',
GTCY_Labels, pageText,
GTCY_Active, page,
TAG_END);
setGadStart = myGad.ng_TopEdge + myGad.ng_Height + INTERHEIGHT * 3;
/*
* We want a separate set of gadgets for Interface.
*/
setGads[0] = NULL;
prevGad2 = CreateContext(&(setGads[0]));
/*
* Create the Gadget Font button.
*/
myGad.ng_LeftEdge = INTERWIDTH * 2;
myGad.ng_TopEdge = setGadStart;
myGad.ng_Width = buttonWidth;
myGad.ng_Height = height + INTERHEIGHT;
myGad.ng_GadgetText = "_Gadget Font...";
myGad.ng_GadgetID = GFONTGAD;
myGad.ng_Flags = 0;
prevGad2 = propFontButGad = CreateGadget(BUTTON_KIND, prevGad2, &myGad,
GT_Underscore, '_',
TAG_END);
/*
* Create the Gadget Font text box.
*/
myGad.ng_LeftEdge += myGad.ng_Width + INTERWIDTH;
myGad.ng_Width = textWidth;
myGad.ng_GadgetText = NULL;
myGad.ng_GadgetID = GBOXGAD;
prevGad2 = propFontGad = CreateGadget(TEXT_KIND, prevGad2, &myGad,
GTTX_Border, TRUE,
GTTX_Text, propString,
TAG_END);
/*
* Create the List Font button.
*/
myGad.ng_LeftEdge = INTERWIDTH * 2;
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_Width = buttonWidth;
myGad.ng_GadgetText = "_List Font...";
myGad.ng_GadgetID = LFONTGAD;
prevGad2 = monoFontButGad = CreateGadget(BUTTON_KIND, prevGad2, &myGad,
GT_Underscore, '_',
TAG_END);
/*
* Create the List Font text box.
*/
myGad.ng_LeftEdge += myGad.ng_Width + INTERWIDTH;
myGad.ng_Width = textWidth;
myGad.ng_GadgetText = NULL;
myGad.ng_GadgetID = LBOXGAD;
prevGad2 = monoFontGad = CreateGadget(TEXT_KIND, prevGad2, &myGad,
GTTX_Border, TRUE,
GTTX_Text, monoString,
TAG_END);
/*
* Create the Refresh cycle gadget, lined up just below the text boxes
* above.
*/
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_Height += 2; /* looks better with an extra couple of pixels! */
myGad.ng_GadgetText = "_Window Type";
myGad.ng_GadgetID = REFRESHGAD;
myGad.ng_Flags = PLACETEXT_LEFT;
prevGad2 = refreshGad = CreateGadget(CYCLE_KIND, prevGad2, &myGad,
GT_Underscore, '_',
GTCY_Labels, refreshText,
GTCY_Active, refresh,
TAG_END);
/*
* Create the Open On cycle gadget, lining it up as above.
*/
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_GadgetText = "_Open On";
myGad.ng_GadgetID = OPENGAD;
prevGad2 = openGad = CreateGadget(CYCLE_KIND, prevGad2, &myGad,
GT_Underscore, '_',
GTCY_Labels, openText,
GTCY_Active, open,
TAG_END);
gadgetEnd1 = myGad.ng_TopEdge + myGad.ng_Height;
/*
* Here's a new set of gadgets for Commodity.
*/
setGads[1] = NULL;
prevGad4 = CreateContext(&(setGads[1]));
/*
* Create the Install checkbox.
*/
myGad.ng_LeftEdge = INTERWIDTH * 2;
myGad.ng_TopEdge = setGadStart + offset;
myGad.ng_Width = 26;
myGad.ng_Height = size;
myGad.ng_GadgetText = "_Install as a Commodity";
myGad.ng_GadgetID = COMGAD;
myGad.ng_Flags = PLACETEXT_RIGHT;
prevGad4 = comGad = CreateGadget(CHECKBOX_KIND, prevGad4, &myGad,
GT_Underscore, '_',
GTCB_Scaled, TRUE,
GTCB_Checked, commodity,
TAG_END);
/*
* Create the Popup checkbox. Disable it (and the rest) if PriMan is
* not running as a Commodity.
*/
myGad.ng_TopEdge += max + INTERHEIGHT; /* offset was already added in for us */
myGad.ng_GadgetText = "Popup When _Launched";
myGad.ng_GadgetID = POPUPGAD;
prevGad4 = popupGad = CreateGadget(CHECKBOX_KIND, prevGad4, &myGad,
GT_Underscore, '_',
GTCB_Scaled, TRUE,
GTCB_Checked, popup,
GA_Disabled, !commodity,
TAG_END);
/*
* Create the Hotkey text box.
*/
myGad.ng_LeftEdge += TextLength(&myRast, "Priority", 8) + INTERWIDTH;
myGad.ng_TopEdge += max - offset + INTERHEIGHT;
myGad.ng_Width = innerWidth - myGad.ng_LeftEdge;
myGad.ng_Height = height + INTERHEIGHT + 2;
myGad.ng_GadgetText = "_Hotkey";
myGad.ng_GadgetID = HOTKEYGAD;
myGad.ng_Flags = PLACETEXT_LEFT;
prevGad4 = hotkeyGad = CreateGadget(STRING_KIND, prevGad4, &myGad,
GT_Underscore, '_',
GTST_String, hotkey,
GTST_MaxChars, MaxHotkey,
GA_Disabled, !commodity,
TAG_END);
/*
* Create the Commodity Priority integer box.
*/
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT;
myGad.ng_Width = intWidth;
myGad.ng_GadgetText = "Priorit_y";
myGad.ng_GadgetID = PRIORITYGAD;
prevGad4 = priorityGad = CreateGadget(INTEGER_KIND, prevGad4, &myGad,
GT_Underscore, '_',
GTIN_Number, priority,
GTIN_MaxChars, 4, /* e.g., -128 */
GA_Disabled, !commodity,
TAG_END);
gadgetEnd3 = myGad.ng_TopEdge + myGad.ng_Height;
/*
* Here's a new set of gadgets for General.
*/
setGads[2] = NULL;
prevGad3 = CreateContext(&(setGads[2]));
/*
* Create the Task Priority integer box, initialising it to our current
* priority.
*/
myGad.ng_LeftEdge = INTERWIDTH * 2 + TextLength(&myRast, "Task Priority", 13) + INTERWIDTH;
myGad.ng_TopEdge = setGadStart;
myGad.ng_GadgetText = "Task Priorit_y";
myGad.ng_GadgetID = TOOLPRIGAD;
prevGad3 = toolpriGad = CreateGadget(INTEGER_KIND, prevGad3, &myGad,
GT_Underscore, '_',
GTIN_Number, FindTask(NULL) -> tc_Node.ln_Pri,
GTIN_MaxChars, 4, /* e.g., -128 */
TAG_END);
/*
* Create the Confirm Actions checkbox.
*/
myGad.ng_LeftEdge = INTERWIDTH * 2;
myGad.ng_TopEdge += myGad.ng_Height + INTERHEIGHT + offset;
myGad.ng_Width = 26;
myGad.ng_Height = size;
myGad.ng_GadgetText = "Confirm _Actions";
myGad.ng_GadgetID = CONFIRMGAD;
myGad.ng_Flags = PLACETEXT_RIGHT;
prevGad3 = confirmGad = CreateGadget(CHECKBOX_KIND, prevGad3, &myGad,
GT_Underscore, '_',
GTCB_Scaled, TRUE, /* new in 3.0 */
GTCB_Checked, confirm,
TAG_END);
/*
* Create the Iconify checkbox.
*/
myGad.ng_TopEdge += max + INTERHEIGHT;
myGad.ng_GadgetText = "_Iconify When Closed";
myGad.ng_GadgetID = ICONIFYGAD;
prevGad3 = iconifyGad = CreateGadget(CHECKBOX_KIND, prevGad3, &myGad,
GT_Underscore, '_',
GTCB_Scaled, TRUE,
GTCB_Checked, iconify,
TAG_END);
gadgetEnd2 = myGad.ng_TopEdge + max - offset;
/*
* Now that we have laid out the three sets of gadgets, we know exactly
* how big the window must be. We calculate the true end of the
* gadgets, and then place the Save, Use & Cancel buttons below this.
*/
gadgetEnd = gadgetEnd1 > gadgetEnd2 ? gadgetEnd1 : gadgetEnd2;
gadgetEnd = gadgetEnd > gadgetEnd3 ? gadgetEnd : gadgetEnd3;
setGadHeight = gadgetEnd - setGadStart;
/*
* Create the Save button. Disable it if PriMan's .info file cannot be
* found, since we won't have any icon in which to store the ToolTypes!
*/
myGad.ng_LeftEdge = INTERWIDTH;
myGad.ng_TopEdge = gadgetEnd + INTERHEIGHT * 3;
myGad.ng_Width = cancelWidth;
myGad.ng_Height = height + INTERHEIGHT;
myGad.ng_GadgetText = "_Save";
myGad.ng_Flags = 0;
myGad.ng_GadgetID = SAVEGAD;
prevGad1 = saveGad = CreateGadget(BUTTON_KIND, prevGad1, &myGad,
GT_Underscore, '_',
GA_Disabled, myIcon == NULL,
TAG_END);
/*
* Create the Use button, evenly distributing the spacing between the
* three buttons.
*/
myGad.ng_LeftEdge += myGad.ng_Width + spacing;
myGad.ng_GadgetText = "_Use";
myGad.ng_GadgetID = USEGAD;
prevGad1 = useGad = CreateGadget(BUTTON_KIND, prevGad1, &myGad,
GT_Underscore, '_',
TAG_END);
/*
* Create the Cancel button.
*/
myGad.ng_LeftEdge += cancelWidth + (setWidth - INTERWIDTH * 2 - cancelWidth * 3) / 2;
myGad.ng_GadgetText = "_Cancel";
myGad.ng_GadgetID = CANCELGAD;
prevGad1 = cancelGad = CreateGadget(BUTTON_KIND, prevGad1, &myGad,
GT_Underscore, '_',
TAG_END);
if (prevGad1 && prevGad2 && prevGad3 && prevGad4)
{
/*
* The Settings window is offset from the main window a little -
* moved down and across by the dimensions of the window's close
* gadget.
*/
if (setWindow = OpenWindowTags(NULL,
WA_Title, "PriMan Settings",
WA_Gadgets, setGads[3],
WA_Left, mainWindow -> LeftEdge + CloseBoxWidth,
WA_Top, mainWindow -> TopEdge + windowTop,
WA_Width, setWidth,
WA_Height, myGad.ng_TopEdge + myGad.ng_Height + INTERHEIGHT + myScreen -> WBorBottom,
WA_DragBar, TRUE,
WA_DepthGadget, TRUE,
WA_Activate, TRUE,
WA_RMBTrap, TRUE,
refresh ? WA_SimpleRefresh : WA_SmartRefresh, TRUE,
WA_PubScreen, myScreen,
TAG_END))
{
setWindow -> UserPort = winPort;
ModifyIDCMP(setWindow, WINDOWIDCMP);
DrawSettingsBox();
GT_RefreshWindow(setWindow, NULL);
oldPage = page; /* take a copy of the gadget page that was last showing */
page = -1; /* next function will see that no gadgets are currently visible */
NewPage(oldPage); /* display whatever the user last saw */
}
else
error = SETTINGS_ERROR;
}
else
error = GADGET_ERROR;
}
/*
* Generate the task list, and place it in the main window's ListView. If
* the task has an appropriate CLI number, that will be added in before its
* name. If it is frozen, its name will be bracketed. If a task was already
* selected, we will attempt to keep it selected.
*/
void CreateList(struct Remember **memoryKey)
{
int i; /* task count */
struct Node *execNode; /* index into Exec's task list */
struct ListType *taskNode; /* storage space for task info */
struct Task *taskArray[MaxTasks]; /* pointers to task info */
/*
* The main window's titlebar is actually set here.
*/
static char *title = "PriMan - 999 tasks";
/*
* This is the error message the user sees if there are more tasks in
* the system than our hard-coded maximum (very unlikely!).
*/
struct EasyStruct task =
{
sizeof(struct EasyStruct),
0,
"PriMan trouble",
"Too many tasks in system -\nlist may be incomplete.",
"Okay"
};
/*
* If we haven't yet established what the top entry in the ListView was
* (top is -1), now would be a good time. (The code that handles window
* resizing gets the top itself since it causes the whole ListView to
* be freed up before it gets this far.) If this is our first time
* here, top will be 0 which is what we want anyway.
*/
if (top == -1)
GetListTop();
/*
* Here we remove the old task list from the ListView, and free up the
* memory it occupied (we can do the freeing even if the memory key was
* NULL already).
*/
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, ~0,
TAG_END);
FreeRemember(memoryKey, TRUE);
/*
* We are going to do three passes through the task list:
*
* 1. Fill taskArray with a list of task addresses. We do this while
* interrupts are disabled, so the pointers remain valid and the
* task lists don't get modified. Hence it must happen very fast!
* The very first entry will be this task, since it doesn't appear
* on either the Ready or Waiting queues. taskCount is the next
* entry to be filled in the array.
*
* 2. Copy the task names and priorities into individual columnised
* strings inside ListType structures, using the pointers we placed
* in taskArray above. This happens while interrupts are enabled
* but multitasking disabled, so that vital things such as timer and
* serial interrupts can happen. Since multitasking is disabled, no
* tasks can actually exit (so our pointers remain valid), although
* it is possible for the task lists to be resorted (something which
* we don't need to worry about since we already walked them above).
*
* 3. After enabling multitasking, we can set about the time-consuming
* business of sorting the task list. Since we already have the
* names and priorities, we don't care what happens to the physical
* tasks from here on.
*
* Here is the first pass...
*/
taskArray[0] = FindTask(NULL);
taskCount = 1;
/*
* Remember that we want to disable interrupts during the first
* pass, and just multitasking during the second pass. So we'll
* nest the Forbid() and Disable() statements, allowing us to
* simply do an Enable() after the first pass to enable interrupts,
* but leave multitasking disabled.
*/
Forbid();
Disable();
/*
* Here we walk both the Ready and Waiting queues, copying the task
* pointers in each into the array. The loop can prematurely abort
* if we reach the maximum allowable number of tasks (array size).
*/
for (execNode = SysBase -> TaskReady.lh_Head;
execNode -> ln_Succ && taskCount < MaxTasks;
execNode = execNode -> ln_Succ)
taskArray[taskCount++] = (struct Task *)execNode;
for (execNode = SysBase -> TaskWait.lh_Head;
execNode -> ln_Succ && taskCount < MaxTasks;
execNode = execNode -> ln_Succ)
taskArray[taskCount++] = (struct Task *)execNode;
/*
* This Enable() will allow interrupts to continue, but not
* multitasking (remember we also did a Forbid()), so the data in
* the task lists will remain static.
*/
Enable();
/*
* We're now onto the second pass. We allocate a Node for each task
* processed (we actually use our ListType structure, which is a
* superset of a Node), and enough memory to fit taskLength characters
* which is where our complete task name and priority (two columns)
* will be put. We use a Remember structure since that's the easiest
* way of keeping track of all these small memory allocations, and
* freeing them later on.
*/
for (i = 0; i < taskCount && !error; i++)
{
if ((taskNode = AllocRemember(memoryKey, sizeof(struct ListType), MEMF_ANY))
&& (taskNode -> mainNode.ln_Name = AllocRemember(memoryKey, taskLength + 1, MEMF_ANY)))
{
/*
* Here we make a copy of the original pointer to the task
* (so we can later kill it, change its priority, etc.), copy
* its priority into the Node structure, fill in the neatly-
* joined name and priority using another function, and finally
* update its pointer in taskArray to point to the Node
* structure, since that's what we'll be using in future.
*/
taskNode -> mainTask = taskArray[i];
taskNode -> mainNode.ln_Pri = taskArray[i] -> tc_Node.ln_Pri;
CreateString(taskArray[i], taskNode -> mainNode.ln_Name);
taskArray[i] = (struct Task *)taskNode;
}
else
{
/*
* We ran out of memory trying to allocate memory for a single
* Node structure. Panic!!
*/
error = TASK_ERROR;
FreeRemember(memoryKey, TRUE);
}
}
/*
* We need nothing more from Exec's goldmine of information, so
* allow things to continue normally and hope that the machine was
* not locked up for a noticeable period of time! After that, we only
* continue if the second pass went okay.
*/
Permit();
if (!error)
{
/*
* The third pass involves creating a List structure, suitable for
* adding to the ListView. Into that structure we sort all the Node
* structures created above. The first thing to do is get a clean
* List...
*/
taskList.lh_Head = (struct Node *) &(taskList.lh_Tail);
taskList.lh_Tail = NULL;
taskList.lh_TailPred = (struct Node *) &(taskList.lh_Head);
/*
* Now we go through all the entries in taskArray, adding them to
* this List structure in alphabetical order. For each entry, we go
* around in a loop as long as there are more tasks to compare
* against in the list and the current task in the list is
* alphabetically earlier than the one we're working on now. We use
* a special version of strcmp() which is case-insensitive and
* skips over leading brackets. Unfortunately, it doesn't sort CLI
* tasks properly (10 will come before 2) - sorry!
*/
for (i = 0; i < taskCount; i++)
{
for (execNode = taskList.lh_Head;
(execNode -> ln_Succ) &&
CompareTasks(execNode -> ln_Name, ((struct ListType *)taskArray[i]) -> mainNode.ln_Name) < 0;
execNode = execNode -> ln_Succ)
; /* empty body */
/*
* Once the loop has ended, we have just passed the position
* where we want to insert our task.
*/
Insert(&taskList, (struct Node *)(taskArray[i]), execNode -> ln_Pred);
}
/*
* If there was a task already selected, we want to find it in the
* new ListView, and reselect it. Recall that current points to the
* selected ListView entry, currentTask points to the task
* structure, and pos is the ordinal number of the selection. If no
* task has been selected, current is NULL, pos is -1, and
* currentTask is undefined.
*/
if (current)
for (execNode = taskList.lh_Head, current = NULL, pos = 0;
execNode -> ln_Succ;
execNode = execNode -> ln_Succ, pos++)
if (((struct ListType *)execNode) -> mainTask == currentTask)
{
current = execNode;
break;
}
/*
* If current is still NULL down here, it means there was
* either no task selected before, or else the one selected could
* no longer be found. Otherwise, we still have a selected task,
* and need to update the slider gadget and menu items, just in
* case its priority has changed in the meantime.
*/
if (current)
OnTask();
else
OffTask();
/*
* We need to move the currently-selected entry into view. We
* use the GTLV_Top tag for starters, since we'll always have a
* reasonable value for that.
*/
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, &taskList,
GTLV_Selected, pos,
GTLV_Top, top,
TAG_END);
/*
* If there is a task selected, we also use the GTLV_MakeVisible
* tag (which only works under 3.x) to try even harder to restore
* the ListView to its original position. We need to do this in a
* separate step to the first call above, since the second tag
* completely overrides the first if they are used together.
*/
if (current)
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_MakeVisible, pos,
TAG_END);
/*
* Now we invalidate "top", so we'll know whether or not to fill it
* in at the start of this function the next time round.
*/
top = -1;
/*
* Here we update PriMan's title bar to reflect the number of tasks
* listed.
*/
sprintf(title, "PriMan - %ld tasks", taskCount);
SetWindowTitles(mainWindow, title, (UBYTE *)~0);
/*
* If necessary, we need to disable the slider, break and kill
* gadgets, and associated menu items.
*/
GT_SetGadgetAttrs(sliderGad, mainWindow, NULL,
GA_Disabled, pos == -1,
TAG_END);
GT_SetGadgetAttrs(breakGad, mainWindow, NULL,
GA_Disabled, pos == -1,
TAG_END);
GT_SetGadgetAttrs(killGad, mainWindow, NULL,
GA_Disabled, pos == -1,
TAG_END);
/*
* We allow the situation where there were more tasks than our
* hard-coded maximum. Since this isn't the user's fault (he
* didn't run out of memory; we're just being lazy!), it's probably
* polite to let him know that the list will be incomplete.
*/
if (taskCount == MaxTasks)
SimpleRequest(&task, NULL);
}
}
/*
* Given a Task structure, extract the name and priority, then join them
* together to fit exactly in taskLength characters.
*
* It is possible that the task is actually a CLI process, in which case
* we need to add "CLI #x" before it.
*
* If the task is frozen, the portion of its name that is visible must be
* bracketed. Our task (excuse the pun) is made easier since we know that
* there is always enough space in the ListView for at least one letter of
* the task name.
*
* There must be at least length+1 bytes free in dest, because of the \0
* character.
*/
void CreateString(struct Task *task, char *dest)
{
int taskChars, /* max number of chars of the task name we can use */
length, /* length of task name */
i, j; /* index into task name */
LONG cli; /* task's CLI process number */
BOOL frozen; /* task is frozen */
char digits[5], /* enough to hold "-128" and \0 char */
cliText[11], /* enough to hold "CLI #999: " and \0 char */
*name; /* pointer to task name */
APTR bstr; /* BCPL structs are used if task is a CLI process */
/*
* The first thing we'll do is convert the task priority into a
* sequence of characters. This will allow us to see exactly how many
* letters in the task name we can fit. Recall that sprintf() returns
* the number of characters it processes. We also allow room for a
* space character between the name and priority.
*/
sprintf(digits, "%ld", task -> tc_Node.ln_Pri);
taskChars = taskLength - strlen(digits) - 1;
/*
* If the task was frozen, we want to bracket its name. Here we insert
* the opening bracket, set a flag to remind us to close it again, and
* then update taskChars if necessary to allow for the brackets.
*/
if (Frozen(task))
{
(*dest++) = '(';
frozen = TRUE;
taskChars -= 2;
}
else
frozen = FALSE;
/*
* Now we obtain a pointer to the task name and get its length, in
* preparation for copying it. If the task is a process and has a CLI
* attached, we fill in the cliText variable and use that first when
* copying.
*/
if (((task -> tc_Node.ln_Type) == NT_PROCESS) &&
(cli = ((struct Process *)task) -> pr_TaskNum))
{
sprintf(cliText, "CLI #%ld: ", cli);
/*
* This next bit is hairy, thanks to BCPL. The CLI name is
* actually a BCPL string, i.e. it has the length in its
* first byte, and doesn't contain a string terminator. So
* we need to walk through some BCPL structures to find the
* CLI name, and then extract its length from that.
*/
bstr = BADDR(((struct CommandLineInterface *)
(BADDR(((struct Process *)task) -> pr_CLI))) -> cli_CommandName);
name = (char *)(bstr) + 1;
length = *((BYTE *)bstr);
}
else
{
/*
* Tasks without a CLI structure have it much easier! We just need
* to remember to add a \0 character to the start of cliText so we
* don't try copying it later.
*/
name = task -> tc_Node.ln_Name;
length = strlen(name);
cliText[0] = '\0';
}
/*
* Copying the task name involves a character-by-character copy of
* both the "CLI #x" text if applicable, and the task name. The loop
* ends when:
*
* - We run out of space in dest (taskChars reaches 0), or;
*
* - We have finished copying both the CLI text (character at current
* position is \0), and the task name (length reaches 0).
*/
i = j = 0;
while (taskChars-- && (cliText[i] || length))
if (cliText[i])
*(dest++) = cliText[i++];
else
{
*(dest++) = name[j++];
length--; /* only decrement this if we've moved onto the task name! */
}
/*
* We mustn't forget to close our brackets if the task was frozen.
*/
if (frozen)
*(dest++) = ')';
/*
* At this point, we might have finished copying the task name, but we
* still need extra spacing characters. We'll add 2 to taskChars - one
* extra obligatory space (accounted for at the start of the function),
* and one to counter the effect of it reaching -1 at the end of the
* previous loop (instead of 0).
*/
taskChars += 2;
while (taskChars--)
*(dest++) = ' ';
/*
* Adding the priority is easy, since we know we're at the exact right
* position for it now. A \0 char will get added on automatically here.
*/
strcpy(dest, digits);
}